package com.chart.common.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Utility class for handling error bar related calculations and data preprocessing.
 *
 * Author: 罗继全
 * Date: 2024/9/28
 */
public class ErrorUtils {

    /**
     * Extracts and preprocesses data.
     *
     * @param rawData The original data list.
     * @return A list of lists containing Double values grouped by columns.
     */
    public static List<List<Double>> extractAndPreprocessData(List<List<String>> rawData) {
        return rawData.stream()
                .map(row -> row.stream()
                        .map(value -> {
                            try {
                                return Double.parseDouble(value);
                            } catch (NumberFormatException e) {
                                // Handle parsing errors; can choose to skip or set to 0
                                return 0.0;
                            }
                        })
                        .collect(Collectors.toList()))
                .collect(Collectors.toList());
    }

    /**
     * Calculates the average for each group of data.
     *
     * @param groupedData The data grouped by columns.
     * @return A list of average values for each group.
     */
    public static List<Double> calculateAverages(List<List<Double>> groupedData) {
        return groupedData.stream()
                .map(column -> column.stream().mapToDouble(Double::doubleValue).average().orElse(0.0))
                .collect(Collectors.toList());
    }

    /**
     * Calculates the standard deviation for a given dataset.
     *
     * @param data The data list.
     * @param mean The average of the data.
     * @return The calculated standard deviation.
     */
    public static double calculateStandardDeviation(List<Double> data, double mean) {
        double variance = data.stream()
                .mapToDouble(value -> Math.pow(value - mean, 2))
                .sum();
        if (data.size() < 2) {
            return 0.0; // Prevent division by zero
        }
        variance /= (data.size() - 1); // Use sample standard deviation
        return Math.sqrt(variance);
    }

    /**
     * Calculates upper and lower limits for each group of data using standard deviation.
     *
     * @param groupedData The data grouped by columns.
     * @param averages    The average values for each group.
     * @return A list containing upper and lower limits for each group.
     */
    public static List<double[]> calculateLimitsUsingStandardDeviation(List<List<Double>> groupedData, List<Double> averages) {
        List<double[]> limits = new ArrayList<>();
        for (int i = 0; i < groupedData.size(); i++) {
            List<Double> column = groupedData.get(i);
            double stdDev = calculateStandardDeviation(column, averages.get(i));
            double newUpper = averages.get(i) + stdDev;
            double newLower = averages.get(i) - stdDev;
            limits.add(new double[]{newUpper, newLower});
        }
        return limits;
    }

    /**
     * Pairs group names with their corresponding averages.
     *
     * @param lenNameList List of group names.
     * @param averages    List of average values.
     * @return A list of paired group data.
     */
    public static List<GroupData> pairGroupData(List<String> lenNameList, List<Double> averages) {
        List<GroupData> group = new ArrayList<>();
        for (int i = 0; i < averages.size(); i++) {
            String name = lenNameList.get(i);
            Double average = averages.get(i);
            group.add(new GroupData(name, average));
        }
        return group;
    }

    /**
     * Formats the calculated limits into JSON ArrayNodes.
     *
     * @param names        List of group names.
     * @param limits       Upper and lower limits for each group.
     * @param objectMapper An instance of ObjectMapper.
     * @return A list of formatted ArrayNodes.
     */
    public static List<ArrayNode> formatLimits(List<String> names, List<double[]> limits, ObjectMapper objectMapper) {
        List<ArrayNode> formattedLimits = new ArrayList<>();
        for (int i = 0; i < names.size(); i++) {
            ArrayNode node = objectMapper.createArrayNode();
            node.add(names.get(i));
            node.add(limits.get(i)[0]);
            node.add(limits.get(i)[1]);
            formattedLimits.add(node);
        }
        return formattedLimits;
    }

    /**
     * Custom GroupData class for storing group name and average value.
     */
    public static class GroupData {
        private String name;
        private Double average;

        public GroupData(String name, Double average) {
            this.name = name;
            this.average = average;
        }

        public String getName() {
            return name;
        }

        public Double getAverage() {
            return average;
        }

        @Override
        public String toString() {
            return "GroupData{name='" + name + "', average=" + average + '}';
        }
    }
}
